shadow element, 它的命名就透露出它不是個外顯的 UI 元件,實際上它的確不會繪製出任何東西到瀏覽器上,只是在元件樹上控制元件的生成與消滅,如同陰影般隱藏在元件背後的幕後操盤手。以下方的片段來說:
<div>
<if test="${user.editable}">
User Name: <textbox value="${user.name}"/>
<forEach items="${user.phones}" var="phone">
<label value="${phone.number}"/>
</forEach>
</if>
</div>

解析完 zul 之後會產生左邊的元件樹。
執行期時,會根據 <if> 中 EL 表達式算出來的結果來決定要不要生成其下的 <textbox> <label>。<forEach> 會根據 ${user.phones} 數量來決定要產生幾個 。而 shadow 元件本身並不會產生對應的元件實體,瀏覽器端也不會繪製出任何 DOM 元素。
Shadow 元件共有以下這些:
<apply>: 範本插入,可以插入事先定義的範本或是 zul 檔<forEach>:走訪集合物件來控制元件生成<if>:條件判斷,如果給定的 EL 結果為 true 就新建其包含的元件,如果為 false 就移除其包含的元件<choose> <when> <otherwise>:條件判斷,如同 java 中的 switch, case, default在我們用 zul 建構畫面時,有時會發現某些元件組合或是某個頁面的區塊重複出現,最直接的方式是「複製—貼上」,但更好的方式能夠直接重用該片段。ZK 以往提供的方式是用 ,8.0 之後則可使用 shadow 元件 <apply>,效果更好。
我推薦優先使用 <apply> ,理由如下:
<apply> 不會產生任何 DOM 元素<include> 會產生一層 <div>,有時候多一個 <div>會影響畫面排版<apply> 不會佔用記憶體<include> 本身是 UI 元件,因此本身仍會佔用記憶體<apply> 不會產生 ID space。<include> 會產生一個 ID space,導致要 @Wire 元件時要 selector 語法較複雜<apply> 可以接受檔案路徑或範本名稱<include> 只能接受路徑<apply> 就如同 inline 的概念一樣,由 ZK 把要引入的 zul 片段,由 zk 幫你動態的插入到目的頁面上。
如果填入字串值或是 EL,是屬於靜態用法,因為只在元件創建的時候估值,創建完之後伺服器就不需要保留 <apply> 物件,因此 <apply> 本身不佔記憶體:
<apply templateURI="customerDetails.zul" />
可透過 EL 來加上簡單的判斷邏輯:
<apply template="${currentUser.hasEditPermission ? 'editable' : 'readonly'}">
<template name="readonly">
<label value="${person.name}"/>
</template>
<template name="editable">
<textbox value="${person.name}"/>
</template>
</apply>
如果想要在控制器中,使範本重新創建其下的元件時,就要設定 dynamicValue="true",之後在範本內容變更時,可呼叫 Apply.recreate() 重新創建元件來更新瀏覽器畫面。
假如 ${currentUser.hasEditPermission} 有可能會變化,我希望能更新畫面,就要這麼寫:
<apply id="personBox"
template="${currentUser.hasEditPermission ? 'editable' : 'readonly'}"
dynamicValue="true">
<template name="readonly">
<label value="${person.name}"/>
</template>
<template name="editable">
<textbox value="${person.name}"/>
</template>
</apply>
${currentUser.hasEditPermission} 已經變了,只要呼叫 recreate() 來重建範本,就能在 <label> 跟 <textbox> 間切換